import path from 'path';
import { omit } from 'lodash';
import type { Loader, TransformOptions, TransformResult } from 'esbuild';
import { transform } from 'esbuild';
import type { SourceMap, Plugin } from 'rollup';
import { createFilter } from '@rollup/pluginutils';

export const cleanUrl = (url: string) => url.replace(/#.*$/, '').replace(/\?.*$/, '');

export interface ESBuildOptions extends TransformOptions {
  include?: string | RegExp | string[] | RegExp[];
  exclude?: string | RegExp | string[] | RegExp[];
}

export type ESBuildTransformResult = Omit<TransformResult, 'map'> & {
  map: SourceMap;
};

export async function transformWithEsbuild(
  code: string,
  filename: string,
  options?: TransformOptions,
): Promise<ESBuildTransformResult> {
  const ext = path.extname(/\.\w+$/.test(filename) ? filename : cleanUrl(filename));
  const loader = ext.slice(1);

  const result = await transform(code, {
    loader: (loader === 'cjs' || loader === 'mjs' ? 'js' : loader) as Loader,
    sourcemap: true,
    sourcefile: filename,
    ...omit(options, ['include', 'exclude']),
  });
  return {
    ...result,
    map: JSON.parse(result.map),
  };
}

export function esbuildPlugin(options: ESBuildOptions = {}): Plugin {
  const filter = createFilter(options.include || /\.(tsx?|jsx)$/, options.exclude || /\.js$/);

  return {
    name: 'seepfly:esbuild',
    async transform(code, id) {
      if (filter(id) || filter(cleanUrl(id))) {
        const result = await transformWithEsbuild(code, id, options);
        return {
          code: result.code,
          map: result.map,
        };
      }
      return null;
    },
  };
}

export const buildEsbuildPlugin = (config: { minify: boolean }): Plugin => {
  return {
    name: 'seepfly:esbuild-build',
    async renderChunk(code, chunk) {
      return transformWithEsbuild(code, chunk.fileName, {
        target: 'es2019',
        minify: config.minify,
      });
    },
  };
};
